package com.rkc.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.rkc.enums.BizCodeEnum;
import com.rkc.enums.CouponCategoryEnum;
import com.rkc.enums.CouponPublishEnum;
import com.rkc.enums.CouponStateEnum;
import com.rkc.exception.BizException;
import com.rkc.interceptor.LoginInterceptor;
import com.rkc.mapper.CouponMapper;
import com.rkc.mapper.CouponRecordMapper;
import com.rkc.model.CouponDO;
import com.rkc.model.CouponRecordDO;
import com.rkc.model.LoginUser;
import com.rkc.request.NewUserCouponRequest;
import com.rkc.service.CouponService;
import com.rkc.utils.CommonUtil;
import com.rkc.utils.JsonData;
import com.rkc.vo.CouponVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 冉堃赤
 * @since 2021-10-22
 */
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {

    @Resource
    private CouponMapper couponMapper;

    @Resource
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {
        Page<CouponDO> pageInfo = new Page<>(page, size);
        Page<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>().eq("publish", CouponPublishEnum.PUBLISH)
                .eq("category", CouponCategoryEnum.PROMOTION)
                .orderByDesc("create_time"));
        Map<String, Object> results = new HashMap<>(3);
        results.put("total_record", couponDOPage.getTotal());
        results.put("total_page", couponDOPage.getPages());
        results.put("current_data", couponDOPage.getRecords().stream().map(this::beanProcess).collect(Collectors.toList()));
        return results;
    }

    /**
     * 领取优惠券
     * 1、查看优惠券是否存在
     * 2、校验优惠券是否可以领取：时间、库存、超过限制
     * 3、扣减库存
     * 4、保存领券记录
     * @param couponId
     * @param category
     * @return
     */
    @SuppressWarnings("ConstantConditions")
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData addCoupon(long couponId, CouponCategoryEnum category) {
        LoginUser loginUser = LoginInterceptor.THREAD_LOCAL.get();
        CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>().eq("id", couponId)
                .eq("category", category.name())
                .eq("publish", CouponPublishEnum.PUBLISH));
        if (couponDO == null) {
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }
        String lockKey = "lock:coupon:" + couponId;
        RLock rLock = redissonClient.getLock(lockKey);
        try {
            //其余线程进入会阻塞，加锁默认30秒过期，有watch dog自动续期功能；lock.lock(30, TimeUnit.SECONDS)指定过期时间，但没有watch dog功能
            rLock.lock();
            log.info("加锁成功：{}", Thread.currentThread().getName());
            checkAndAddCoupon(couponId, loginUser, couponDO);
        } finally {
            rLock.unlock();
            log.info("解锁成功：{}", Thread.currentThread().getName());
        }
        return JsonData.buildSuccess();
    }

    /**
     * 当用户微服务调用时，用户还没有进行登录，也就是没有token
     * 如果本地直接调用发放优惠券的方法，需要构造一个用户存储在ThreadLocal
     * @param newUserCouponRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserCouponRequest.getUserId());
        loginUser.setName(newUserCouponRequest.getName());
        LoginInterceptor.THREAD_LOCAL.set(loginUser);
        //查询新用户可以领取的优惠券
        List<CouponDO> couponDOList = couponMapper.selectList(new QueryWrapper<CouponDO>().eq("category", CouponCategoryEnum.NEW_USER.name()));
        for (CouponDO couponDO : couponDOList) {
            //幂等操作，需要加锁
            addCoupon(couponDO.getId(), CouponCategoryEnum.NEW_USER);
        }
        return JsonData.buildSuccess();
    }

    private void checkAndAddCoupon(long couponId, LoginUser loginUser, CouponDO couponDO) {
        //优惠券是否可以领取
        checkCoupon(couponDO, loginUser.getId());
        //校验通过，创建领券记录
        CouponRecordDO couponRecordDO = new CouponRecordDO();
        BeanUtils.copyProperties(couponDO, couponRecordDO);
        couponRecordDO.setCreateTime(new Date());
        couponRecordDO.setUseState(CouponStateEnum.NEW.name());
        couponRecordDO.setUserId(loginUser.getId());
        couponRecordDO.setCouponId(couponId);
        couponRecordDO.setId(null);
        int rows = couponMapper.reduceStock(couponId);
        if (rows == 1) {
            //库存扣减成功才保存记录
            couponRecordMapper.insert(couponRecordDO);
        } else {
            log.warn("发放优惠券失败：{}, 用户：{}", couponDO, loginUser);
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
    }

    private void checkCoupon(CouponDO couponDO, long id) {
        //检查库存
        if (couponDO.getStock() <= 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
        //检查时间
        long time = CommonUtil.getCurrentTimestamp();
        long start = couponDO.getStartTime().getTime(), end = couponDO.getEndTime().getTime();
        if (time < start || time > end) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }
        //用户是否超过领取限制
        Integer recordCount = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>().eq("coupon_id", couponDO.getId())
                .eq("user_id", id));
        if (recordCount >= couponDO.getUserLimit()) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }

    private CouponVO beanProcess(CouponDO obj) {
        CouponVO couponVO = new CouponVO();
        BeanUtils.copyProperties(obj, couponVO);
        return couponVO;
    }
}
